/**传入Flysel的遍历方法的回调函数 */
interface ergodicCallBlck {
    (flysel: Flysel, index: number, srcFlysel: ThisType<Flysel>): any | Promise<any>,
}

/**元素事件选项 */
interface ListenerOption {
    /**是否在事件捕获阶段触发 */
    capture?: boolean,
    /**在添加之后最多只调用一次 */
    once?: boolean,
    /**禁用preventDefault()阻止默认行为 */
    passive?: boolean,
    /**事件中止信号 */
    signal?: AbortController
    /**委托选择器 */
    entrust?: string
}

/**元素事件映射 */
interface ElementEventMap extends HTMLElementEventMap {
    /**鼠标持续按下并滑动 */
    mousepressmove: MouseEvent
    stylechange: StyleEvent
}

/**类数组对象 */
interface ClassArray {
    length: number
    [index: number]: any
}

/**对象索引 */
interface ObjectIndex<V = any> {
    [k: string]: V
}

/**表单收集类选项 */
interface FormJsonOption {
    /**输入元素选择器 */
    input?: string
    /**复数元素选择器 */
    plural?: string
    /**默认/缺省数据 */
    defaultValue?: { [x: string]: any }
}

class StyleEvent extends Event {
    style: string
    value: string
    constructor(type = 'stylechange', option?: any) {
        super(type, option)
        this.style = option.style
        this.value = option.value
    }
}

/**可省略单位属性 */
const STYLEDEGREE = new Set([
    'width',
    'maxWidth',
    'minWidth',
    'height',
    'maxHeight',
    'minHeight',
    'left',
    'right',
    'top',
    'bottom',
    'padding',
    'margin',
    'fontSize',
    'lineHeight',
])

/**FormJson默认选项 */
const defaultFormJsonOption = {
    input: 'input[name][type="radio"]:checked:enabled,input[name][type="checkbox"]:checked:enabled,input[name]:not([type="radio"],[type="checkbox"],[type="submit"]):enabled,select[name]:enabled,textarea[name]:enabled',
    plural: 'input[name][type="checkbox"]',
    defaultValue: {}
}

export class FlyselError extends Error { }

/**form表单收集 */
export class FormJson {
    [x: string]: any
    #form
    #option
    constructor(form: Flysel, option: FormJsonOption = {}) {
        const op = Object.assign({}, defaultFormJsonOption, option)
        form?.find(op.plural).forEach(e => {
            op.defaultValue[e.name] = new Set
        })
        this.#form = form
        this.#option = op
        this.collect()
    }
    set(name: string, value: any, element?: HTMLElement) {
        const attr = this[name]
        if (element?.hasAttribute('number')) value = + value
        if (attr?.add) {
            attr.add(value)
            return attr
        }
        if (element?.matches(this.#option.plural))
            return this[name] = new Set([value])
        else
            return this[name] = value
    }
    collect() {
        if (this.#option.defaultValue) Object.assign(this, this.#option.defaultValue)
        if (this.#form) {
            const fs = this.#form.find(this.#option.input)
            for (let i = 0; i < fs.length; i++) {
                const e = fs[i] as HTMLInputElement;
                const k = e.getAttribute('name') as string
                this.set(k, e.value, e)
            }
        }
    }
    getOption() {
        return this.#option
    }
}

/**事件侦听器函数 */
interface EventListener<T extends keyof ElementEventMap = any> {
    (this: Flysel, event: ElementEventMap[T]): any
}

/**Flysel事件处理类选项映射 */
interface FlyselEventOptions {
    once: boolean
}

/**Flysel事件处理类事件映射 */
interface FlyselEventMap { }

/**Flysel事件系统 */
export class FlyselEvent<H extends FlyselEvent<H, M>, M extends FlyselEventMap> {
    #listenerMap: Map<string | number | symbol, Set<any>> = new Map
    onaddlistener: ((this: H, event: { type: string, listener: (this: any, event: any) => void }) => void) | null = null
    onremovelistener: ((this: H, event: { type: string, listener: (this: any, event: any) => void, hasDelete: boolean }) => void) | null = null
    onemitevent: ((this: H, event: { type: string, length: number }) => void) | null = null
    /**
     * 侦听事件
     * @param type 事件类型
     * @param listener 事件侦听器
     * @param option 事件选项
     * @returns 
     */
    on<T extends keyof M>(type: T, listener: (this: H, event: M[T]) => void, option?: FlyselEventOptions) {
        let ls = this.#listenerMap.get(type)
        if (!ls) {
            ls = new Set
            this.#listenerMap.set(type, ls)
        }
        //@ts-ignore
        this.onaddlistener && this.onaddlistener({ type, listener })
        ls.add(listener)
        if (option?.once) {
            const x = () => {
                ls?.delete(listener)
                ls?.delete(x)
            }
        }
        return true
    }
    /**
     * 取消事件侦听器
     * @param type 事件类型
     * @param listener 事件侦听器
     * @param option 事件选项
     * @returns 
     */
    off<T extends keyof M>(type: T, listener: (this: H, event: M[T]) => void) {
        const set = this.#listenerMap.get(type)
        if (set) {
            const hasDelete = set.delete(listener)
            // @ts-ignore
            this.onremovelistener && this.onremovelistener({ type, listener, hasDelete })
            return hasDelete
        }
        return false
    }
    /**
     * 发生事件
     * @param type 事件类型
     * @param event 事件参数
     * @returns
     */
    emit<T extends keyof M>(type: T, event: M[T]) {
        const set = this.#listenerMap.get(type)
        if (set) {
            set.forEach(ls => {
                ls.call(this, event)
            })
            //@ts-ignore
            this.onemitevent && this.onemitevent({ type, length: set.size })
            return true
        }
        return false
    }
}

/**钩子事件 */
interface HookEventMap extends FlyselEventMap {
    on: { flysel: Flysel, type: keyof ElementEventMap, listener: EventListener, option?: ListenerOption },
    off: { flysel: Flysel, type: keyof ElementEventMap, listener: EventListener }
    instantiation: { flysel: Flysel, element: HTMLElement | any }
}

/**内置事件（钩子函数） */
export const hook = new FlyselEvent<FlyselEvent<any, HookEventMap>, HookEventMap>()

/**
 * Flying Select 类
 */
export class Flysel<E extends HTMLElement = HTMLElement> {
    [index: number]: E
    [key: string]: any
    splice() { }
    /**长度 */
    length: number = 0
    constructor(element?: E) {
        if (element) this.add(element)
        Object.defineProperty(this, 'length', {
            enumerable: false,
            value: element ? 1 : 0
        })
        hook.emit('instantiation', { flysel: this, element })
    }
    /**返回第一个原生E对象 */
    get e() {
        return this[0]
    }
    /**
     * 获取指定索引的Flysel对象
     * @param {number} index 索引
     * @returns {Flysel}
     */
    get(index: number): Flysel {
        const e = this[index]
        return new Flysel(e)
    }
    /**
     * 添加元素
     * @param element 
     * @returns
     */
    add(element: E) {
        return this[this.length] = element, this.length++, this
    }
    /**
     * 过滤元素
     * @param {ergodicCallBlck} callBlck 回调函数
     */
    filter(callBlck: ergodicCallBlck) {
        // @ts-ignore
        if (callBlck[Symbol.toStringTag] === 'AsyncFunction') {
            return (async () => {
                const flysel: Flysel = new Flysel
                for (let index = 0; index < this.length; index++) {
                    const element = this[index];
                    if (await callBlck(new Flysel(element), index, this)) flysel.add(element)
                }
                console.log('n');
                return flysel
            })()
        } else {
            const flysel: Flysel = new Flysel
            for (let index = 0; index < this.length; index++) {
                const element = this[index];
                if (callBlck(new Flysel(element), index, this)) flysel.add(element)
            }
            return flysel
        }
    }
    /**筛选 */
    screen(selector: string) {
        const fs = new Flysel
        for (let i = 0; i < this.length; i++) {
            const e = this[i];
            if (e.matches(selector)) fs.add(e)
        }
        return fs
    }
    /**
     * 遍历元素
     * @param {ergodicCallBlck} callBlck 回调函数
     */
    forEach(callBlck: ergodicCallBlck) {
        // @ts-ignore
        if (callBlck[Symbol.toStringTag] === 'AsyncFunction') {
            return (async () => {
                for (let index = 0; index < this.length; index++) {
                    const element = this[index];
                    await callBlck(new Flysel(element), index, this)
                }
            })()
        } else {
            for (let index = 0; index < this.length; index++) {
                const element = this[index];
                callBlck(new Flysel(element), index, this)
            }
        }
    }
    /**
     * 遍历元素并返回一个新数组
     * @param {ergodicCallBlck} callBlck 回调函数
     */
    map(callBlck: ergodicCallBlck) {
        // @ts-ignore
        if (callBlck[Symbol.toStringTag] === 'AsyncFunction') {
            return (async () => {
                const array = []
                for (let index = 0; index < this.length; index++) {
                    const element = this[index];
                    array.push(await callBlck(new Flysel(element), index, this))
                }
                return array
            })()
        } else {
            const array = []
            for (let index = 0; index < this.length; index++) {
                const element = this[index];
                array.push(callBlck(new Flysel(element), index, this))
            }
            return array
        }
    }
    /**
     * 返回是否存在指定element
     * @param element 
     * @returns 
     */
    includes(element: any) {
        if (element instanceof Flysel) element = element[0]
        for (let i = 0; i < this.length; i++) {
            if (element.isEqualNode(this[i])) return true
        }
        return false
    }
    /**
     * 转为字符串
     * @param element 
     * @returns 
     */
    toString(element: E): any {
        if (element) {
            if (element instanceof HTMLElement) {
                const cs = element.classList
                let cns = ''
                for (let index = 0; index < cs.length; index++) {
                    cns += '.' + cs[index]
                }
                const id = element.id
                return element.tagName.toLocaleLowerCase() + (id ? '#' + id : '') + cns
            } else {
                return element
            }
        } else {
            const array = []
            for (let i = 0; i < this.length; i++) {
                array.push(this.toString(this[i]))
            }
            return `Flysel(${this.length}) [${array.join(', ')}]`
        }
    }
    /**
     * 获取相对窗口的位置
     * @returns 
     */
    offwin() {
        let d = this[0];
        let l = d.offsetLeft;
        let t = d.offsetTop;
        while (true) {
            // @ts-ignore
            d = d.offsetParent;
            if (!d) {
                l -= window.scrollX;
                t -= window.scrollY;
                return { top: t, left: l };
            }
            l += d.offsetLeft;
            l -= d.scrollLeft;
            t += d.offsetTop;
            t -= d.scrollTop;
        }
    }
    /**
     * 
     * @returns 获取相对html顶部的位置
     */
    offset() {
        let d = this[0];
        let l = d.offsetLeft;
        let t = d.offsetTop;
        while (true) {
            // @ts-ignore
            d = d.offsetParent;
            if (!d) {
                return { top: t, left: l };
            }
            l += d.offsetLeft;
            t += d.offsetTop;
        }
    }
    /**收集表单 */
    collect(option?: FormJsonOption) {
        return new FormJson(this, option)
    }

    /**选择方法 */
    /**
     * 从后代中选择
     * @param {string} selector 
     * @returns
     */
    find<E extends HTMLElement = HTMLElement>(selector: string = '*'): Flysel<E> {
        const fs: Flysel<E> = new Flysel
        for (let fi = 0; fi < this.length; fi++) {
            const ns = this[fi].querySelectorAll(selector)
            for (let ni = 0; ni < ns.length; ni++) {
                fs.add(ns[ni] as E)
            }
        }
        return fs
    }
    /**
     * 从子代中选择
     * @param {string} selector 
     * @returns 
     */
    child<E extends HTMLElement = HTMLElement>(selector: string = '*'): Flysel<E> {
        selector = ':scope>' + selector
        const fs: Flysel<E> = new Flysel
        for (let fi = 0; fi < this.length; fi++) {
            const ns = this[fi].querySelectorAll(selector)
            for (let ni = 0; ni < ns.length; ni++) {
                fs.add(ns[ni] as E)
            }
        }
        return fs
    }
    /**
     * 从前辈元素中选择
     * @param {string} selector 
     */
    parent<E extends HTMLElement = HTMLElement>(selector: string): Flysel<E> {
        const fs: Flysel<E> = new Flysel
        for (let i = 0; i < this.length; i++) {
            const pe = selector ? this[i].closest(selector) : this[i].parentElement;
            if (pe && !fs.includes(pe)) fs.add(pe as E)
        }
        return fs
    }
    /**
     * 从兄弟元素中选择
     * @param {string} selector 
     * @returns 
     */
    sibling<E extends HTMLElement = HTMLElement>(selector: string = '*'): Flysel<E> {
        const fs: Flysel<E> = new Flysel
        for (let fi = 0; fi < this.length; fi++) {
            const e = this[fi]
            const pe = e.parentElement
            if (!pe) continue
            const es = pe.querySelectorAll(':scope>' + selector)
            for (let ei = 0; ei < es.length; ei++) {
                const b = es[ei] as E;
                if (!e.isEqualNode(b)) fs.add(b)
            }
        }
        return fs
    }

    /**操作类名 */
    /**
     * 添加类名
     * @param {string[]} className 
     * @returns 
     */
    addClass(...className: string[]) {
        for (let i = 0; i < this.length; i++) {
            this[i].classList.add(...className)
        }
        return this
    }
    /**
     * 删除类名
     * @param {string[]} className 
     * @returns 
     */
    removeClass(...className: string[]) {
        for (let i = 0; i < this.length; i++) {
            this[i].classList.remove(...className)
        }
        return this
    }
    /**
     * boolean为true则添加类名反之则删除类名
     * @param {Boolean} boolean
     * @param {string[]} className 
     * @returns 
     */
    booleClass(boolean: Boolean, ...className: string[]) {
        if (boolean) {
            for (let i = 0; i < this.length; i++) {
                this[i].classList.add(...className)
            }
        } else {
            for (let i = 0; i < this.length; i++) {
                this[i].classList.remove(...className)
            }
        }
        return this
    }
    /**
     * 检测是否存在指定类名
     * @param {string} className 
     * @returns 
     */
    hasClass(className: string) {
        return this[0].classList.contains(className)
    }
    /**
     * 若存在已类名则删除类名，反之则添加类名
     * @param {string} className 
     * @returns 
     */
    toggleClass(...className: string[]) {
        for (let i = 0; i < this.length; i++) {
            const classLiet = this[i].classList
            if (classLiet.contains(className[0]))
                classLiet.remove(...className)
            else
                classLiet.add(...className)
        }
        return this
    }
    /**
     * 活跃类名：添加指定类名并删除所有兄弟元素的指定类名
     * @param {string[]} className 
     * @returns 
     */
    activeClass(...className: string[]) {
        for (let i = 0; i < this.length; i++) {
            const e = this[i]
            const pe = e.parentElement
            if (!pe) continue
            const es = pe.querySelectorAll(':scope>*')
            for (let ei = 0; ei < es.length; ei++) {
                const b = es[ei]
                if (e.isEqualNode(b))
                    b.classList.add(...className)
                else
                    b.classList.remove(...className)
            }
        }
        return this
    }

    /**操作事件 */
    /**
     * 添加事件侦听器
     * @param {string} type 
     * @param {FlyselListener} listener 
     * @returns 
     */
    on<T extends keyof ElementEventMap>(listenerObject: { [type in T]: EventListener<T> }, option?: ListenerOption): this
    on<T extends keyof ElementEventMap>(type: T, listener: EventListener<T>, option?: ListenerOption): this
    on<T extends keyof ElementEventMap>(ags1: T | { [type in T]: EventListener<T> }, ags2?: ListenerOption | EventListener<T>, ags3?: ListenerOption) {
        if (typeof ags1 === 'object') {
            for (const key in ags1) {
                // @ts-ignore
                this.on(key, ags1[key], ags2)
            }
        } else {
            if (ags1.includes(',')) {
                const ts = ags1.split(/,|, /)
                for (let i = 0; i < ts.length; i++) {
                    // @ts-ignore
                    this.on(ts[i], ags2, ags3)
                }
                return this
            }
            // @ts-ignore
            hook.emit('on', { flysel: this, type: ags1, listener: ags2, option: ags3 })
            for (let i = 0; i < this.length; i++) {
                const e = this[i]
                // @ts-ignore
                const liser = ags3?.entrust ? (function (event: Event) {
                    // @ts-ignore
                    const e = event.target.closest(ags3.entrust)
                    if (e) {
                        // @ts-ignore
                        ags2.call(new Flysel(e))
                    }
                }) :
                    // @ts-ignorec
                    ags2.bind(new Flysel(e))
                // @ts-ignore
                if (ags2.name) {
                    // @ts-ignore
                    if (e._liserMap) {
                        // @ts-ignore
                        e._liserMap.set(ags2, liser)
                    } else {
                        // @ts-ignore
                        e._liserMap = new Map([[ags2, liser]])
                    }
                }
                // @ts-ignore
                e.addEventListener(ags1, liser, ags3)
            }
        }
        return this
    }
    /**
     * 移除事件侦听器
     * @param {string} type 
     * @param {FlyselListener} listener 
     * @returns
     */
    off<T extends keyof ElementEventMap>(type: T, listener: EventListener<T>) {
        for (let i = 0; i < this.length; i++) {
            const e = this[i];
            // @ts-ignore
            const rl = e._liserMap?.get(listener)
            if (rl) {
                // @ts-ignore
                e.removeEventListener(type,), e._liserMap.delete(listener)
            } else {
                // @ts-ignore
                e.removeEventListener(type, listener)
            }
        }
        return this
    }

    /**常用方法代理 */
    /**
     * 设置css
     * @param style style对象
     */
    css(style: string): string
    /**
     * 设置css
     * @param style style对象
     */
    css(style: CSSStyleDeclaration): this
    /**
     * 设置css
     * @param attr css属性名
     * @param value css属性值
     */
    css<T extends keyof CSSStyleDeclaration>(attr: T, value: CSSStyleDeclaration[T]): this
    css<T extends keyof CSSStyleDeclaration>(ags1: CSSStyleDeclaration | T, ags2?: any) {
        if (typeof ags1 === 'object') {
            for (const key in ags1) {
                let value = ags1[key]
                if (typeof value !== 'string' && STYLEDEGREE.has(key)) value = value + 'px'
                //@ts-ignore
                this.css(key, value)
            }
            return this
        } else if (ags2 !== undefined) {
            // @ts-ignore
            if (typeof ags2 !== 'string' && STYLEDEGREE.has(ags1)) ags2 = ags2 + 'px'
            for (let ei = 0; ei < this.length; ei++) {
                this[ei].style[ags1] = ags2
                this[ei].dispatchEvent(new StyleEvent('stylechange', {
                    style: ags1,
                    value: ags2
                }))
            }
            return this
        } else {
            return this[0].style[ags1]
        }
    }
    upIncludes(selector: string) {
        for (let i = 0; i < this.length; i++) {
            if (this[i].closest(selector)) return true
        }
        return false
    }
    /**
     * 在元素内添加元素节点或文本
     * @param {...(string | Element)[]} eles
     * @returns 
     */
    append(...eles: (string | Element)[]) {
        for (let i = 0; i < this.length; i++) {
            for (let ei = 0; ei < eles.length; ei++) {
                const e = eles[ei];
                if (typeof e === 'string') {
                    this[i].insertAdjacentHTML('beforeend', e)
                } else {
                    this[i].insertAdjacentElement('beforeend', e)
                }
            }
        }
        return this
    }
    /**
     * 在元素内添加HTML
     * @param html 
     * @returns 
     */
    addHTML(html: string) {
        for (let i = 0; i < this.length; i++) {
            this[i].insertAdjacentHTML('beforeend', html)
        }
        return this
    }
    /**
     * 在元素内添加节点
     * @param element 
     * @returns 
     */
    addElement(element: Element) {
        for (let i = 0; i < this.length; i++) {
            this[i].insertAdjacentElement('beforeend', element)
        }
        return this
    }
    /**
     * 插入HTML
     * @param where 插入位置 
     * @param html 
     * @returns 
     */
    insertHTML(where: InsertPosition, html: string) {
        for (let i = 0; i < this.length; i++) {
            this[i].insertAdjacentHTML(where, html)
        }
        return this
    }
    /**
     * 插入Element
     * @param where 插入位置 
     * @param Element 
     * @returns 
     */
    insertElement(where: InsertPosition, element: Element) {
        for (let i = 0; i < this.length; i++) {
            this[i].insertAdjacentElement(where, element)
        }
        return this
    }
    /**
     * 返回一个包含所有元素的attr属性的数组（对象属性）
     * @param {string} attr 
     */
    prop(attr: string): any
    /**
     * 将所有元素的attr属性设为value（对象属性）
     * @param attr 
     * @param value 
     */
    prop(attr: string, value: any): this
    prop(attr: string, value?: any) {
        if (value) {
            for (let i = 0; i < this.length; i++) {
                // @ts-ignore
                this[i][attr] = value
            }
            return this
        } else {
            // @ts-ignore
            return this[0][attr]
        }
    }
    /**
     * 返回一个包含所有元素的attr属性的数组（内联属性）
     * @param {string} attr 
     */
    attr(attr: string): any
    /**
     * 将所有元素的attr属性设为value（内联属性）
     * @param attr 
     * @param value 
     */
    attr(attr: string, value: any): this
    attr(attr: string, value?: any) {
        if (value) {
            for (let i = 0; i < this.length; i++) {
                // @ts-ignore
                this[i].setAttribute(attr, value)
            }
            return this
        } else {
            return this[0].getAttribute(attr)
        }
    }
    /**
     * 获取对象属性为数组
     * @param {string} prop 
     * @returns 
     */
    getProp(prop: string) {
        const ar = []
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            ar.push(this[0][prop])
        }
        return ar
    }
    /**
     * 获取内联属性为数组
     * @param {string} attr 
     * @returns 
     */
    getAttr(attr: string) {
        const ar = []
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            ar.push(this[0].getAttribute(attr))
        }
        return ar
    }
    /**
     * 模板渲染
     * @param data 
     * @param option 
     */
    template(data: any, option: { preset?: boolean }) {
        if (!this._render) this._render = template(this.html, option)
        let hl = this._render(data)
        if (hl.startsWith('<!--'))
            hl = hl.slice(4, -3)
        this.html = hl
    }
    /**
     * 滑动相对距离
     * @param x 
     * @param y 
     */
    scrollBy(x?: number, y?: number): this
    /**
     * 滑动相对距离
     * @param options 
     */
    scrollBy(options?: ScrollToOptions): this
    scrollBy(x?: number | ScrollToOptions, y?: number) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].scrollBy(x, y)
        }
        return this
    }
    /**
     * 滑动至指定距离
     * @param x 
     * @param y 
     */
    scrollTo(x?: number, y?: number): this
    /**
     * 滑动至指定距离
     * @param options 
     */
    scrollTo(options?: ScrollToOptions): this
    scrollTo(x?: number | ScrollToOptions, y?: number) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].scrollTo(x, y)
        }
        return this
    }
    /**触发click */
    click() {
        for (let i = 0; i < this.length; i++) {
            this[i].click()
        }

        return this
    }
    /**触发focus */
    focus() {
        for (let i = 0; i < this.length; i++) {
            this[i].focus()
        }

        return this
    }
    /**触发blur */
    blur() {
        for (let i = 0; i < this.length; i++) {
            this[i].blur()
        }
        return this
    }
    /**隐藏元素(display:none) */
    hide() {
        for (let i = 0; i < this.length; i++) {
            this[i].style.display = 'none'
        }
        return this
    }
    /**显示元素(display:block) */
    block() {
        for (let i = 0; i < this.length; i++) {
            this[i].style.display = 'block'
        }
        return this
    }
    /**显示元素(display:flex) */
    flex() {
        for (let i = 0; i < this.length; i++) {
            this[i].style.display = 'flex'
        }
        return this
    }

    /**常用属性代理 */
    /**文本 */
    get text() {
        return this[0].innerText
    }
    set text(v) {
        for (let i = 0; i < this.length; i++) {
            this[i].innerText = v
        }
    }
    /**HTML */
    get html() {
        return this[0].innerHTML
    }
    set html(v) {
        for (let i = 0; i < this.length; i++) {
            this[i].innerHTML = v
        }
    }
    /**元素宽度，等同于offsetWidth */
    get width() {
        return this[0].offsetWidth
    }
    set width(v) {
        for (let i = 0; i < this.length; i++) {
            this[i].style.width = v + 'px'
        }
    }
    /**元素宽度，等同于offsetHeight */
    get height() {
        return this[0].offsetHeight
    }
    set height(v) {
        for (let i = 0; i < this.length; i++) {
            this[i].style.height = v + 'px'
        }
    }
    /**name */
    get name() {
        // @ts-ignore
        return this[0].name
    }
    set name(v) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].name = v
        }
    }
    /**id */
    get id() {
        // @ts-ignore
        return this[0].id
    }
    set id(v) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].id = v
        }
    }
    /**value */
    get value() {
        // @ts-ignore
        let v = this[0].value
        if (this[0].hasAttribute('number')) v = !v
        return v
    }
    set value(v) {
        for (let i = 0; i < this.length; i++) {
            //@ts-ignore
            if (this[i].value !== v) {
                // @ts-ignore
                this[i].value = v
                this[i].dispatchEvent(new InputEvent('input', {
                    data: v,
                    bubbles: true,
                    cancelable: true
                }))
            }
        }
    }
    /**type */
    get type() {
        // @ts-ignore
        return this[0].type
    }
    set type(v) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].type = v
        }
    }
    /**checked */
    get checked() {
        // @ts-ignore
        return this[0].checked
    }
    set checked(v) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].checked = v
        }
    }
    /**checked */
    get src() {
        // @ts-ignore
        return this[0].src
    }
    set src(v) {
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].src = v
        }
    }
    /**left */
    get left() {
        // @ts-ignore
        return this[0].offsetLeft
    }
    set left(v) {
        // @ts-ignore
        if (typeof v === 'number') v += 'px'
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].style.left = v
        }
    }
    /**top */
    get top() {
        // @ts-ignore
        return this[0].offsetTop
    }
    set top(v) {
        // @ts-ignore
        if (typeof v === 'number') v += 'px'
        for (let i = 0; i < this.length; i++) {
            // @ts-ignore
            this[i].style.top = v
        }
    }

    /**只读属性 */
    get clientWidth() {
        // @ts-ignore
        return this[0].clientWidth
    }
    get clientHeight() {
        // @ts-ignore
        return this[0].scrollHeight
    }
    get scrollWidth() {
        // @ts-ignore
        return this[0].scrollWidth
    }
    get scrollHeight() {
        // @ts-ignore
        return this[0].scrollHeight
    }
    get scrollLeft() {
        // @ts-ignore
        return this[0].scrollLeft
    }
    get scrollTop() {
        // @ts-ignore
        return this[0].scrollTop
    }

    /**静态方法 */
    /**
     * 从类数组对象生成实例
     * @param {ClassArray} array 类数组对象
     * @returns 
     */
    static from<E extends HTMLElement = HTMLElement>(array: ClassArray): Flysel<E> {
        const fs: Flysel<E> = new Flysel
        for (let index = 0; index < array.length; index++) {
            fs.add(array[index])
        }
        return fs
    }
}

/**
 * flying select
 * @param selector 
 * @returns 
 */
export const flysel = function (selector: string | HTMLElement | ClassArray = '*', context: HTMLElement | ClassArray | Document = document) {
    if (typeof selector === 'string') {
        // @ts-ignore
        return Flysel.from(context.querySelectorAll ? context.querySelectorAll(selector) : context.find(selector))
    } else if (selector instanceof HTMLElement) {
        return new Flysel(selector)
    } else if (selector.length) {
        return Flysel.from(selector)
    }
    return new Flysel
}

// @ts-ignore
Flysel.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
// @ts-ignore
Flysel.prototype[Symbol.toStringTag] = 'Flysel'

// @ts-ignore
flysel.document = new Flysel(document);
flysel.body = new Flysel(document.body);
flysel.html = new Flysel(document.documentElement);

// @ts-ignore
flysel.on = Flysel.prototype.on.bind(flysel.document)
// @ts-ignore
flysel.off = Flysel.prototype.off.bind(flysel.document)

export default flysel

const forR = /^\{\{for.*?\(.*?\)\}\}$/,
    forRe = /^\{\{for\}\}$/,
    insR = /\{\{[\s\S]*?\}\}/g,
    braR = /\(.*?\)/

const presetStr = `eval(\`var {\${Object.keys(data).join(',')}}=data\`);`

/**
 * 解析模板
 * @param {string} tem 
 * @param {*} option
 */
export function template(tem: string, option?: { preset?: boolean }) {
    let str = tem.trim()
        .replace(insR, s => {
            if (forR.test(s)) {
                const si = s.indexOf('(')
                const ei = s.indexOf(')', si)
                return `\${${s.slice(5, si).trim() || 'data'}.map((${s.slice(si + 1, ei)})=>\``
            }
            if (forRe.test(s)) return '`).join(\'\')}'
            return '${' + s.slice(2, -2) + '}'
        })
    str = 'return `' + str + '`'
    if (option) {
        if (option.preset) {
            str = presetStr + str
        }
    }
    const render = new Function(...Object.keys(builtMethods), 'data', str) as (data: any) => string
    return render.bind(null, ...Object.values(builtMethods))
}
/**模板引擎内置方法 */
const builtMethods = {
    interval(index: number, ...int: string[]) {
        return int[index % int.length]
    }
}

/**插件参数 */
export interface SyncPlugin extends ObjectIndex {
    /**同步对象 */
    object: Object[]
    /**同步表单 */
    form: { el: Flysel | Flysel | string, option: FormJsonOption } | string
    /**同步样式 */
    style: Flysel,
    /**更新事件 */
    change: (event: { key: string, value: any, item?: Value, add?: boolean }) => void
}

type SP = SyncPlugin

const syncPlugin: { [x: string]: Function } = {
    object(sync: Sync, prom: SP["object"]) {
        sync.on('change', function (e) {

        })
    },
    form(sync: Sync, prom: SP['form']) {
        let el: Flysel, option
        if (typeof prom === 'string') {
            el = flysel(prom)
        } else if (prom instanceof Flysel) {
            el = prom
        } else {
            el = typeof prom.el === 'string' ? flysel(prom.el) : prom.el
            option = prom.option
        }
        // 初始化form数据
        const json = new FormJson(el, option)
        Object.assign(sync.proxy, json)
        // form同步到sync
        el.on('input', (eve) => {
            const ele = eve.target as HTMLInputElement
            const name = ele.name
            const value = ele.value
            if (name && ele) {
                if (ele.type === 'radio') {
                    if (ele.checked) {
                        sync.set(name, value)
                    }
                } else if (ele.type === 'checkbox') {
                    sync.item(name, value, ele.checked)
                } else {
                    sync.set(name, value)
                }
            }
        })
        // sync同步到form
        sync.on('set', e => {
            const fe = el.find(`[name="${e.key}"]`)
            for (let i = 0; i < fe.length; i++) {
                const ele = fe[i] as HTMLInputElement
                const t = ele.type
                if (t === 'checkbox') throw new FlyselError('SyncPlugin[form]:"set"事件不能同步到[type="checkbox"]元素')
                if (ele.type === 'radio') {
                    if (ele.value === e.value?.toString()) {
                        ele.checked = true
                    }
                } else {
                    ele.value = e.value?.toString() || ''
                }
            }
        })
        sync.on('item', e => {
            const fe = el.find(`input[name="${e.key}"][value="${e.item}"]`)
            for (let i = 0; i < fe.length; i++) {
                const ele = fe[i] as HTMLInputElement
                const t = ele.type
                if (t !== 'checkbox') throw new FlyselError(`SyncPlugin[form]:"item"事件不能同步到[type="${t}"]元素`)
                ele.checked = !!e.add
            }
        })
    },
    style(sync: Sync, prom: SP['style']) {
        prom.on('stylechange', (eve) => {
            const ele = eve.target as HTMLElement
            const name = ele.getAttribute('name')
            if (name) {
                sync.set(name, ele.getAttribute('style') || '')
            }
        })
        sync.on('set', e => {
            for (let i = 0; i < prom.length; i++) {
                if (prom[i].getAttribute('name') === e.key)
                    prom[i].setAttribute('style', e.value?.toString() || '')
            }
        })
    },
    change(sync: Sync, prom: SP['change']) {
        sync.on('change', prom)
    }
}

/**同步选项 */
type SyncOption = SyncPlugin | ObjectIndex

/**支持同步的数据类型 */
type Value = string | number | undefined

/**Sync选项映射 */
interface SyncFlyselEventMap extends FlyselEventMap {
    change: { key: string, value: Value | Set<Value>, item?: Value, add?: boolean }
    set: { key: string, value: Value | Set<Value> }
    item: { key: string, item: Value, add?: boolean }
    add: { key: string, item: any }
    del: { key: string, item: any }
}

/**数据同步 */
export class Sync<S extends ObjectIndex = any> extends FlyselEvent<Sync, SyncFlyselEventMap>  {
    src: S
    proxy: S
    constructor(src: S, option: SyncOption = {}) {
        super()
        this.src = src
        //代理对象
        this.proxy = new Proxy(src, {
            get: (o, p) => {
                const v = o[<string>p]
                if (v instanceof Set) return new SetSync(v, this, <string>p)
                return v
            },
            set: (o, p, v) => {
                this.set(<string>p, v)
                return true
            }
        })
        // 运行同步插件
        const ks = Object.keys(option)
        for (let i = 0; i < ks.length; i++) {
            const k = ks[i];
            const v = option[k]
            syncPlugin[k](this, v)
        }
    }
    set(key: string, value: Value | Set<Value>) {
        const t = typeof value
        if (t !== 'string' && t !== 'number' && t !== 'undefined' && !(value instanceof Set))
            throw new FlyselError('不受支持的数据类型[' + t + '],仅支持[string][number][undefined][Set]')
        const { src } = this
        if (src[key] === value) return false
        //@ts-ignore
        src[key] = value
        this.emit('change', { key, value })
        this.emit('set', { key, value })
        return true
    }
    item(key: string, item: Value, add?: boolean) {
        const t = typeof item
        if (t !== 'string' && t !== 'number' && t !== 'undefined')
            throw new FlyselError('不受支持的数据类型[' + t + '],仅支持[string][number][undefined]')
        if (add) {
            this.add(key, item)
        } else {
            this.del(key, item)
        }
        this.emit('change', { key, value: this.src[key], add })
        this.emit('item', { key, item, add })
    }
    add(key: string, item: Value) {
        const { src } = this
        const set = src[key]
        if (set instanceof Set) {
            set.add(item)
        } else {
            //@ts-ignore
            src[key] = new Set([item])
        }
        this.emit('add', { key, item })
    }
    del(key: string, item: Value) {
        const { src } = this
        const set = src[key]
        if (set instanceof Set) {
            set.delete(item)
        } else {
            //@ts-ignore
            src[key] = new Set()
        }
        this.emit('del', { key, item })
    }
    toJSON() {
        const obj: any = {}
        const ks = Object.keys(this.src)
        for (let i = 0; i < ks.length; i++) {
            const k = ks[i];
            const v = this.src[k]
            if (v instanceof Set) {
                obj[k] = Array.from(v)
            } else {
                const t = typeof v
                if (t === 'string' || t === "number") {
                    obj[k] = v
                } else if (v.toString) {
                    obj[k] = v.toString()
                }
            }
        }
        return obj
    }
}

/**Set同步 */
export class SetSync {
    #src
    #sync
    #key
    constructor(src = new Set, sync: Sync, key: string) {
        this.#src = src
        this.#sync = sync
        this.#key = key
    }
    get size() {
        return this.#src.size
    }
    has(v: any) {
        return this.#src.has(v)
    }
    clear() {
        return this.#src.clear()
    }
    add(value: any) {
        if (!this.#src.has(value)) {
            this.#sync.item(this.#key, value, true)
            this.#src.add(value)
        }
        return this
    }
    delete(value: any) {
        if (this.#src.has(value)) {
            this.#sync.item(this.#key, value)
            this.#src.delete(value)
        }
        return false
    }
    entries() {
        return this.#src.entries()
    }
    forEach(callBack: (value: any, value2: any, set: Set<any>) => void) {
        return this.#src.forEach(callBack)
    }
    del(value: any) {
        return this.delete(value)
    }
}

/**
 * 数据同步
 * @param option 配置
 * @returns 
 */
export function sync<S>(src: S, option: SyncOption = {}) {
    const sync = new Sync<S>(src, option)
    return sync.proxy
}

// 自定义事件：mousepressmove
{
    let e: HTMLElement | null
    flysel.document.on({
        mousedown(ev) {
            e = ev.target as HTMLElement
        },
        mousemove(ev) {
            e?.dispatchEvent(new MouseEvent('mousepressmove', ev))
        },
        mouseup() {
            e = null
        }
    })
}